package vsvm.classifier.optimization;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

import vsvm.gui.forms.OutputFrame;

public class GeneticOptimizer extends AbstractOptimizer {
	private int nPop = 8;
	private int nKeep = 4;
	private int bits = 32;
	private int nGenerations = 30;
	private double mutProb = 0.2;
	private OutputFrame out;
	
	public GeneticOptimizer(OutputFrame f) {
		out = f;
	}
	
	private class Chromosome implements Comparable {
		public String genes;
		public double value;
		
		public Chromosome() {
			genes = "";
		}
		
		public int compareTo(Object o) {
			Chromosome c =(Chromosome)o;
			
			return value < c.value ? 1 : -1;
		}		
	}
	

	private ArrayList<Chromosome> population;
	
	private void setParameters(OptimizableParameter[] params, Chromosome c) {
		int ix = 0;
		for (int i = 0; i < params.length; ++i) {
			double s = params[i].getStart();
			double e = params[i].getEnd();
			for (int j = 0; j < bits-1; ++j) {
				if (c.genes.charAt(ix) == '1')
					s = (s+e)/2;
				else
					e = (s+e)/2;
				++ix;
			}
			if (c.genes.charAt(ix) == '1')
				params[i].setValue(e);
			else
				params[i].setValue(s);
			if (params[i].isExponent())
				params[i].setValue(Math.pow(2, params[i].getValue()));
			++ix;			
		}		
	}
	
	private String paramsToString(OptimizableParameter[] params) {
		String s = "(";
		
		for (int i = 0; i < params.length-1; ++i) {
			s += params[i].getValue() + ", ";
		}
		s += params[params.length-1].getValue() + ")";
		
		return s;		
	}
	
	public double[] optimize(OptimizableParameter[] params, IOptimizable o) {
		
		
		Random r = new Random();
		// init population
		population = new ArrayList<Chromosome>(); 
		for (int i = 0; i < nPop; ++i) {
			Chromosome c = new Chromosome();
			for (int j = 0; j < params.length; ++j) {
				for (int b = 0; b < bits; ++b)
					c.genes += r.nextBoolean() ? "1" : "0"; 
			}
			setParameters(params, c);
			c.value = o.evaluate(params);
			out.log("Evaluated with parameters " + paramsToString(params) + ", accuracy: " + c.value);
			population.add(c);
		}
		
		Collections.sort(population);
		
		for (int i = 0; i < nGenerations; ++i) {
			out.log("Generation " + i + ". Worst survived cost: " + population.get(nKeep-1).value);
			int[] m = mateWeightCost(population);
			//int[] m = matePairs(population);
			for (int j = 0; j < nKeep; j += 2) {				
				int cp = r.nextInt(population.get(m[j]).genes.length()-1);
				Chromosome c1 = new Chromosome();
				Chromosome c2 = new Chromosome();
				StringBuffer sb = new StringBuffer();
				sb.append(population.get(m[j]).genes.substring(0, cp+1));
				sb.append(population.get(m[j+1]).genes.substring(cp+1));
				for (int k = 0; k < sb.length(); ++k) if (r.nextDouble() < mutProb) sb.setCharAt(k, (char)('1' - sb.charAt(k) + '0'));
				c1.genes =  sb.toString();
				sb = new StringBuffer();
				sb.append(population.get(m[j]).genes.substring(cp+1));
				sb.append(population.get(m[j+1]).genes.substring(0, cp+1));
				for (int k = 0; k < sb.length(); ++k) if (r.nextDouble() < mutProb) sb.setCharAt(k, (char)('1' - sb.charAt(k) + '0'));
				c2.genes =  sb.toString();
				
				setParameters(params, c1);
				c1.value = o.evaluate(params);				
				out.log("Evaluated with parameters " + paramsToString(params) + ", accuracy: " + c1.value);
				setParameters(params, c2);
				c2.value = o.evaluate(params);
				out.log("Evaluated with parameters " + paramsToString(params) + ", accuracy: " + c2.value);
				
				population.set(nKeep+j, c1);
				population.set(nKeep+j+1, c2);				
			}
			
			Collections.sort(population);
		}

		setParameters(params, population.get(0));
		double[] res = new double[params.length];
		for (int i = 0; i < params.length; ++i)
			res[i] = params[i].getValue();
		
		out.log("Best accuracy: " + population.get(0).value);
		out.log("Parameters (gamma, C): " + paramsToString(params));
		
		return res;
	}
	
	private int[] matePairs(ArrayList<Chromosome> p) {
		int[] m  = new int[nPop-nKeep];
		
		for (int i = 0; i < nPop-nKeep; ++i)
			m[i] = i;
		
		return m;
	}
	
	private int[] mateWeightCost(ArrayList<Chromosome> p) {
		double ck = p.get(nKeep).value;
		double sum = 0;
		double[] c = new double[nKeep];
		
		for (int i = 0; i < nKeep; ++i)
			sum += p.get(i).value - ck;
		
		c[0] = (p.get(0).value - ck)/sum;
		for (int i = 1; i < nKeep; ++i)
			c[i] = c[i-1] + (p.get(i).value - ck)/sum;
		
		int[] m  = new int[nPop-nKeep];
		Random r = new Random();
		
		for (int i = 0; i < nPop-nKeep; ++i) {
			double q = r.nextDouble();
			int pos = 0;
			while (q > c[pos]) ++pos;
			m[i] = pos;
		}
		
		return m;
	}
}
